home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Skunkware 5
/
Skunkware 5.iso
/
src
/
X11
/
xconq
/
unit.c
< prev
next >
Wrap
C/C++ Source or Header
|
1995-05-09
|
20KB
|
716 lines
/* Copyright (c) 1987, 1988 Stanley T. Shebs, University of Utah. */
/* This program may be used, copied, modified, and redistributed freely */
/* for noncommercial purposes, so long as this notice remains intact. */
#pragma comment(exestr, "@(#) unit.c 12.1 95/05/09 ")
/* RCS $Header: unit.c,v 1.5 88/07/20 15:23:42 shebs Exp $ */
/* This file contains all code relating specifically to units. */
/* Since units appear and disappear with distressing regularity (so it seems */
/* to the player trying to keep up with all of them!), we have a little */
/* storage manager for them. Since this sort of thing is tricky, there is a */
/* two-level procedure for getting rid of units. First the hit points are */
/* reduced to zero, at which point the unit is considered "dead". At the */
/* end of a turn, we actually GC the dead ones. At this point their type */
/* slot becomes NOTHING, and they are available for allocation. */
#include "config.h"
#include "misc.h"
#include "period.h"
#include "side.h"
#include "unit.h"
#include "map.h"
#include "global.h"
char *ordinal();
Unit *units; /* array of all units */
Unit *unitlist; /* pointer to head of list of units */
Unit *tmpunit; /* global temporary used in several places */
char unitbuf[BUFSIZE];
int numunits; /* total number of units in existence */
int maxunits; /* current size of array of units */
int nextid; /* next number to be used for ids */
int occdeath[MAXUTYPES]; /* buffer for remembering occupant death */
/* Init all the unit entries so we can find them when allocating. */
init_units()
{
int i;
maxunits = INITMAXUNITS;
units = (Unit *) malloc(maxunits * sizeof(Unit));
for (i = 0; i < maxunits; ++i) units[i].type = NOTHING;
unitlist = NULL;
numunits = 0;
nextid = 0;
}
/* Create a new unit of given type, with given name. Default all the other */
/* slots - routines that need to will fill them in properly. This routine */
/* will return a valid unit or NULL. Note that unit morale starts out at */
/* max, signifying hopeful but inexperienced recruits marching off to war. */
Unit *
create_unit(type, name)
int type;
char *name;
{
int i, r;
Unit *newunit;
for (i = 0; i < maxunits; ++i) {
if (units[i].type == NOTHING) break;
}
if (i == maxunits) {
if (grow_unit_array()) {
return create_unit(type, name);
} else {
return NULL;
}
}
newunit = &units[i];
newunit->type = type;
if (name == NULL || strlen(name) == 0) {
newunit->name = NULL;
} else {
newunit->name = copy_string(name);
}
newunit->x = newunit->y = -1;
newunit->number = 0;
newunit->side = NULL;
newunit->id = nextid++;
newunit->trueside = NULL;
newunit->hp = utypes[type].hp;
newunit->quality = 0;
newunit->morale = utypes[type].maxmorale;
newunit->fatigue = 0;
newunit->product = NOTHING;
newunit->schedule = 0;
newunit->built = 0;
for_all_resource_types(r) newunit->supply[r] = 0;
newunit->transport = NULL;
newunit->group = 0;
newunit->goal = 0;
newunit->awake = FALSE; /* i.e., is usually not *temporarily* awake */
newunit->standing = NULL;
newunit->occupant = NULL;
newunit->nexthere = NULL;
link_in_unit(newunit);
wake_unit(newunit, FALSE);
++numunits;
return newunit;
}
/* Add unit to front of list. It will be sorted into place shortly. */
link_in_unit(unit)
Unit *unit;
{
unit->next = unitlist;
unitlist = unit;
}
/* Attempt to the number of units that can be played. We do the obvious - */
/* malloc a new array and copy everything over. First copy byte-by-byte, */
/* then adjust non-NULL pointers to other units. Must also take care of */
/* any globals or other variables. (such as in side structs) */
/* At the moment, this seems to cause weird changes in unit behavior which */
/* I don't understand. One missing thing here is to change all the ptrs */
/* on the world map itself... Another solution would be to do in between */
/* turns, perhaps after flushing dead units. */
grow_unit_array()
{
#ifdef GROWABLE
char *base, *newbase;
int newmax, i;
Unit *newunits;
newmax = maxunits + maxunits / 2;
if (Debug) printf("Extending unit array to hold %d units.\n", newmax);
newunits = (Unit *) malloc(newmax * sizeof(Unit));
base = (char *) units;
newbase = (char *) newunits;
for (i = 0; i < maxunits * sizeof(Unit); ++i) newbase[i] = base[i];
for (i = 0; i < maxunits; ++i) {
if (newunits[i].next)
newunits[i].next =
(Unit *) ((char *) units[i].next + (newbase - base));
if (newunits[i].nexthere)
newunits[i].nexthere =
(Unit *) ((char *) units[i].nexthere + (newbase - base));
if (newunits[i].transport)
newunits[i].transport =
(Unit *) ((char *) units[i].transport + (newbase - base));
if (newunits[i].occupant)
newunits[i].occupant =
(Unit *) ((char *) units[i].occupant + (newbase - base));
}
/* Need to patch the map as well, maybe other things? */
for (i = maxunits; i < newmax; ++i) newunits[i].type = NOTHING;
free(units);
maxunits = newmax;
units = newunits;
if (unitlist) unitlist = (Unit *) ((char *) unitlist + (newbase - base));
notify_all("The unit array has been grown.");
return TRUE;
#else
notify_all("Can't make any more units!");
return FALSE;
#endif
}
/* Find a good initial unit for the given side. Only requirement these */
/* days is that it be of the type specified in the period file, and not */
/* already used by somebody. If enough failed tries to find one, */
/* something may be wrong, but not necessarily. */
Unit *
random_start_unit()
{
int tries = numunits;
Unit *unit;
while (tries-- > 0) {
unit = &units[random(numunits)];
if (neutral(unit) && unit->type == period.firstutype) return unit;
}
for_all_units(unit) {
if (neutral(unit) && unit->type == period.firstutype) return unit;
}
return NULL;
}
/* A unit occupies a hex either by entering a unit on that hex or by having */
/* the occupant pointer filled in. If something goes wrong, return false. */
/* This is heavily used. */
occupy_hex(unit, x, y)
Unit *unit;
int x, y;
{
register int u = unit->type, o;
register Unit *other = unit_at(x, y);
if (other) {
o = other->type;
if (could_carry(o, u)) {
occupy_unit(unit, other);
} else if (could_carry(u, o)) {
leave_hex(other);
occupy_hex(unit, x, y);
occupy_hex(other, x, y);
} else {
return FALSE;
}
} else {
set_unit_at(x, y, unit);
occupy_hex_aux(unit, x, y);
all_see_occupy(unit, x, y);
all_see_hex(x, y);
}
return TRUE;
}
/* Recursive helper to update everybody's position. This should be one of */
/* two routine that modify unit positions (leaving is the other). */
/* *Every* occupant will increment viewing coverage - strains realism, */
/* but prevents strange bugs. */
occupy_hex_aux(unit, x, y)
Unit *unit;
int x, y;
{
register Unit *occ;
unit->x = x; unit->y = y;
cover_area(unit, x, y, 1);
for_all_occupants(unit, occ) occupy_hex_aux(occ, x, y);
}
/* Decide whether transport has the capability to house the given unit. */
/* Check both basic capacity and relative volumes. */
can_carry(transport, unit)
Unit *transport, *unit;
{
int u = unit->type, u2 = transport->type, total = 0, volume = 0;
int hold = utypes[transport->type].holdvolume;
Unit *occ;
if (transport == unit)
return FALSE;
for_all_occupants(transport, occ) {
if (occ->type == u) total++;
volume += utypes[occ->type].volume;
}
if (cripple(transport)) {
hold = (hold * transport->hp) / (utypes[u2].crippled + 1);
}
return ((total + 1 <= utypes[u2].capacity[u]) &&
(volume + utypes[u].volume <= hold));
}
/* Units become passengers by linking into front of transport's passenger */
/* list. They only wake up if the transport is a moving type, but always */
/* pick up standing orders if any defined. A passenger will get sorted to */
/* move after transport, at end of turn. */
occupy_unit(unit, transport)
Unit *unit, *transport;
{
Order *newords;
unit->nexthere = transport->occupant;
transport->occupant = unit;
unit->transport = transport;
if (mobile(transport->type)) wake_unit(unit, FALSE);
if (transport->standing != NULL) {
newords = (transport->standing->orders)[unit->type];
if (newords && newords->type != NONE) {
if (Debug) printf("%s getting orders %s",
unit_desig(unit), order_desig(newords));
copy_orders(&(unit->orders), newords);
}
}
occupy_hex_aux(unit, transport->x, transport->y);
all_see_occupy(unit, transport->x, transport->y);
}
/* Unit departs from a hex by zeroing out pointer if in hex or by being */
/* removed from the list of transport occupants. */
/* Dead units (hp = 0) may be run through here, so don't error out. */
leave_hex(unit)
Unit *unit;
{
register int ux = unit->x, uy = unit->y;
if (ux < 0 || uy < 0) {
/* Sometimes called twice */
} else if (unit->transport != NULL) {
leave_unit(unit, unit->transport);
leave_hex_aux(unit);
all_see_leave(unit, ux, uy);
} else {
set_unit_at(ux, uy, NULL);
if (see_exact(unit->side, ux, uy))
draw_hex(unit->side, ux, uy);
/* see_hex(unit->side, ux, uy);*/
leave_hex_aux(unit);
all_see_leave(unit, ux, uy);
all_see_hex(ux, uy);
}
}
/* Trash old coordinates (recursively) just in case. Catches many bugs... */
leave_hex_aux(unit)
Unit *unit;
{
register Unit *occ;
cover_area(unit, unit->x, unit->y, -1);
unit->x = -1; unit->y = -1;
for_all_occupants(unit, occ) leave_hex_aux(occ);
}
/* Disembarking unlinks from the list of passengers. */
leave_unit(unit, transport)
Unit *unit, *transport;
{
Unit *occ;
if (unit == transport->occupant) {
transport->occupant = unit->nexthere;
} else {
for_all_occupants(transport, occ) {
if (unit == occ->nexthere) {
occ->nexthere = occ->nexthere->nexthere;
break;
}
}
}
unit->transport = NULL;
}
/* Handle the general situation of a unit changing allegiance from one side */
/* to another. This is a common internal routine, so no messages here. */
unit_changes_side(unit, newside, reason1, reason2)
Unit *unit;
Side *newside;
int reason1, reason2;
{
Side *oldside = unit->side;
Unit *occ;
if (oldside != NULL) {
oldside->units[unit->type]--;
if (producing(unit)) oldside->building[unit->product]--;
if (reason2 >= 0) oldside->balance[unit->type][reason2]++;
update_state(oldside, unit->type);
}
if (newside == NULL && !utypes[unit->type].isneutral) {
kill_unit(unit, -1);
} else {
if (newside != NULL) {
newside->units[unit->type]++;
if (producing(unit)) newside->building[unit->product]++;
if (reason1 >= 0) newside->balance[unit->type][reason1]++;
update_state(newside, unit->type);
}
for_all_occupants(unit, occ) {
unit_changes_side(occ, newside, reason1, reason2);
}
}
if (alive(unit)) {
cover_area(unit, unit->x, unit->y, -1);
assign_unit_to_side(unit, newside);
cover_area(unit, unit->x, unit->y, 1);
}
}
/* Change the product of a unit. Displays will need appropriate mods. */
set_product(unit, type)
Unit *unit;
int type;
{
if (!neutral(unit) && global.setproduct && type != unit->product) {
if (unit->product != NOTHING) {
unit->side->building[unit->product]--;
update_state(unit->side, unit->product);
}
if (type != NOTHING) {
unit->side->building[type]++;
update_state(unit->side, type);
}
unit->product = type;
unit->built = 0;
unit->movesleft--;
}
}
/* Set product completion time. Startup development cost incurred only */
/* if directed. */
/* Technology development cost only incurred for very first unit that the */
/* side constructs. Both tech and startup are percent addons. */
set_schedule(unit)
Unit *unit;
{
if (producing(unit)) unit->schedule = build_time(unit, unit->product);
}
/* Basic routine to compute how long a unit will take to build something. */
build_time(unit, prod)
Unit *unit;
int prod;
{
int schedule = utypes[unit->type].make[prod];
if (unit->built == 0)
schedule += ((schedule * utypes[prod].startup) / 100);
if (unit->side->counts[prod] <= 1)
schedule += ((schedule * utypes[prod].research) / 100);
return schedule;
}
/* Remove a unit from play. This is different from making it available for */
/* reallocation - only the unit flusher can do that. We remove all the */
/* passengers too, recursively. Sometimes units are "killed twice", so */
/* be sure not to run all this twice. Also count up occupant deaths, being */
/* sure not to count the unit itself as an occupant. */
kill_unit(unit, reason)
Unit *unit;
int reason;
{
int u = unit->type, u2;
if (alive(unit)) {
for_all_unit_types(u2) occdeath[u2] = 0;
leave_hex(unit);
kill_unit_aux(unit, reason);
occdeath[u]--;
}
}
/* Trash it now - occupant doesn't need to leave_hex. Also record a reason */
/* for statistics, and update the apropriate display. The unit here should */
/* be known to be alive. */
kill_unit_aux(unit, reason)
Unit *unit;
int reason;
{
int u = unit->type;
Unit *occ;
unit->hp = 0;
occdeath[u]++;
if (unit->side != NULL && reason >= 0) {
unit->side->balance[u][reason]++;
unit->side->units[u]--;
if (producing(unit)) unit->side->building[unit->product]--;
update_state(unit->side, u);
}
for_all_occupants(unit, occ) if (alive(occ)) kill_unit_aux(occ, reason);
}
/* Get rid of all dead units at once. It's important to get rid of all */
/* of the dead units, so they don't come back and haunt us. */
/* (This routine is basically a garbage collector, and should not be called */
/* during a unit list traversal.) The process starts by finding the first */
/* live unit, making it the head, then linking around all in the middle. */
flush_dead_units()
{
Unit *unit, *unitprev;
while (unitlist != NULL && !alive(unitlist)) {
unit = unitlist->next;
flush_one_unit(unitlist);
unitlist = unit;
}
for_all_units(unitprev) {
unit = unitprev->next;
if (unit != NULL && !alive(unit)) {
unitprev->next = unit->next;
flush_one_unit(unit);
}
}
}
/* Keep it clean - hit all links to other places. Some might not be */
/* strictly necessary, but this is not an area to take chances with. */
flush_one_unit(unit)
Unit *unit;
{
unit->type = NOTHING;
unit->occupant = NULL;
unit->transport = NULL;
unit->nexthere = NULL;
unit->next = NULL;
--numunits;
}
/* Do multiple passes of bubble sort. This is intended to improve locality */
/* among unit positions and reduce the amount of scrolling around. */
/* Data is generally coherent, so bubble sort not too bad if we allow */
/* early termination when everything in order. */
/* If slowness objectionable, replace with something clever. */
sort_units(doall)
bool doall;
{
bool flips = TRUE;
int passes = 0;
register Unit *prevunit, *unit, *nextunit;
while (flips && (doall || passes < numunits / 10)) {
flips = FALSE;
prevunit = NULL;
unit = unitlist;
while (unit != NULL) {
if (unit->next != NULL && !in_order(unit, unit->next)) {
flips = TRUE;
nextunit = unit->next;
if (prevunit != NULL) prevunit->next = nextunit;
unit->next = nextunit->next;
nextunit->next = unit;
prevunit = nextunit;
if (unit == unitlist) unitlist = nextunit;
} else {
prevunit = unit;
unit = unit->next;
}
}
passes++;
}
if (Debug) printf("Sorting passes = %d\n", passes);
}
/* This can be pretty lax, with the exception that passengers *must* come */
/* after their transports, or game saving will screw up. */
in_order(unit1, unit2)
Unit *unit1, *unit2;
{
int d1, d2;
if (side_number(unit1->side) < side_number(unit2->side)) return TRUE;
if (side_number(unit1->side) > side_number(unit2->side)) return FALSE;
if (!neutral(unit1)) {
d1 = distance(unit1->side->cx, unit1->side->cy, unit1->x, unit1->y);
d2 = distance(unit2->side->cx, unit2->side->cy, unit2->x, unit2->y);
if (d1 < d2) return TRUE;
if (d1 > d2) return FALSE;
}
if (unit1->y > unit2->y) return TRUE;
if (unit1->y < unit2->y) return FALSE;
if (unit1->x < unit2->x) return TRUE;
if (unit1->x > unit2->x) return FALSE;
if (unit1->transport == NULL && unit2->transport != NULL) return TRUE;
if (unit1->transport != NULL && unit2->transport == NULL) return FALSE;
if (unit1 == unit2->transport) return TRUE;
if (unit1->transport == unit2) return FALSE;
return TRUE;
}
/* A unit runs low on supplies at the halfway point, but only worries about */
/* the essential items. Formula is the same no matter how/if occupants eat */
/* transports' supplies. */
low_supplies(unit)
Unit *unit;
{
int u = unit->type, r;
for_all_resource_types(r) {
if ((utypes[u].consume[r] > 0) || (utypes[u].tomove[r] > 0)) {
if (2 * unit->supply[r] <= utypes[u].storage[r]) return TRUE;
}
}
return FALSE;
}
/* Display the standing orders of the given unit. */
show_standing_orders(side, unit)
Side *side;
Unit *unit;
{
int u;
Order *ords;
if (unit->standing != NULL) {
sprintf(spbuf, "Orders: ");
for_all_unit_types(u) {
ords = unit->standing->orders[u];
if (ords && ords->type != NONE) {
sprintf(tmpbuf, "%s to %s; ",
utypes[u].name, order_desig(ords));
strcat(spbuf, tmpbuf);
}
}
notify(side, "%s", spbuf);
} else {
notify(side, "No standing orders defined yet.");
}
}
/* Build a short phrase describing a given unit to a given side. */
/* First we supply identification of side, then of unit itself. */
char *
unit_handle(side, unit)
Side *side;
Unit *unit;
{
char *utypename;
if (unit == NULL || !alive(unit)) return "???";
utypename = utypes[unit->type].name;
if (utypes[unit->type].named == 2 && unit->name) return unit->name;
if (unit->side == NULL) {
sprintf(unitbuf, "the neutral ");
} else if (unit->side == side) {
sprintf(unitbuf, "your ");
} else {
sprintf(unitbuf, "the %s ", unit->side->name);
}
if (unit->name != NULL) {
sprintf(tmpbuf, "%s %s", utypename, unit->name);
} else if (unit->number > 0) {
sprintf(tmpbuf, "%s %s", ordinal(unit->number), utypename);
} else {
sprintf(tmpbuf, "%s", utypename);
}
strcat(unitbuf, tmpbuf);
return unitbuf;
}
/* Shorter unit description omits side name, but uses same buffer. */
char *
short_unit_handle(unit)
Unit *unit;
{
if (unit->name == NULL) {
sprintf(unitbuf, "%s %s",
ordinal(unit->number), utypes[unit->type].name);
} else {
sprintf(unitbuf, "%s", unit->name);
}
return unitbuf;
}
/* General-purpose routine to take an array of anonymous unit types and */
/* summarize what's in it, using numbers and unit chars. */
char *
summarize_units(buf, ucnts)
char *buf;
int *ucnts;
{
char tmp[BUFSIZE];
int u;
sprintf(buf, "");
for_all_unit_types(u) {
if (ucnts[u] > 0) {
sprintf(tmp, " %d %c", ucnts[u], utypes[u].uchar);
strcat(buf, tmp);
}
}
return buf;
}
/* Search for a unit with the given id number. */
Unit *
find_unit(n)
int n;
{
Unit *unit;
for_all_units(unit) if (alive(unit) && unit->id == n) return unit;
return NULL;
}
/* Given a unit character, find the type. */
find_unit_char(ch)
char ch;
{
int u;
for_all_unit_types(u) if (utypes[u].uchar == ch) return u;
return NOTHING;
}
/* Generate a name for a unit, using best acrynomese. This is invoked when */
/* a new named unit has been created. */
char *
make_unit_name(unit)
Unit *unit;
{
sprintf(spbuf, "%c%c-%c-%02d",
uppercase(unit->side->name[0]), uppercase(unit->side->name[1]),
utypes[unit->type].uchar, unit->number);
return copy_string(spbuf);
}